package com.project.srtp.secure.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.project.srtp.secure.dto.req.SecureFileReqDTO;
import com.project.srtp.secure.dto.resp.SecureFileRespDTO;
import com.project.srtp.secure.dao.entity.SecureFile;
import com.project.srtp.secure.dao.mapper.SecureFileMapper;
import com.project.srtp.secure.service.SecureService;
import com.project.srtp.secure.util.CryptoUtil;
import com.project.srtp.secure.util.ExceptionUtil;
import com.project.srtp.secure.util.FileStorageUtil;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Optional;

/**
 * @author 曹行阳
 * @version 0.1
 * @date 2025/6/10 16:54
 * SecureServiceImpl：实现加密上传与解密下载逻辑
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class SecureServiceImpl extends ServiceImpl<SecureFileMapper, SecureFile> implements SecureService {
    private final SecureFileMapper secureFileMapper;


    /**
     * 实现文件上传并加密
     */
    @Override
    @Transactional
    public SecureFileRespDTO uploadAndEncrypt(SecureFileReqDTO request) throws Exception {
        MultipartFile multipartFile = request.getFile();
        String ownerId = request.getOwnerId();

        if (multipartFile == null || multipartFile.isEmpty()) {
            throw ExceptionUtil.toResponseStatusException("上传文件为空", HttpStatus.BAD_REQUEST);
        }

        // 2. 生成随机 AES Key 并 Base64 编码保存指纹
        byte[] rawKey = CryptoUtil.generateAesKey();
        String keyFingerprint = Base64.getEncoder().encodeToString(rawKey);

        // 3. 将 MultipartFile 写成临时文件
        File tmpFile = FileStorageUtil.saveMultipartFile(multipartFile);

        // 4. 流式读取 tmpFile 并加密后写入目标路径
        String storagePath;
        try (InputStream fis = new FileInputStream(tmpFile);
             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {

            // 加密，CryptoUtil 内部会把 IV 写在流前 12 字节
            CryptoUtil.encrypt(fis, bos, rawKey);

            // 将加密后字节流写入磁盘，返回 storagePath
            byte[] encryptedBytes = bos.toByteArray();
            try (InputStream encryptedInput = new ByteArrayInputStream(encryptedBytes)) {
                storagePath = FileStorageUtil.writeEncryptedStream(encryptedInput, keyFingerprint);
            }
        } finally {
            try {
                Files.deleteIfExists(tmpFile.toPath());
            } catch (IOException e) {
                log.warn("临时文件删除失败: {}", tmpFile.getAbsolutePath());
            }
        }

        // 5. 在数据库中插入元信息
        SecureFile secureFile = SecureFile.builder()
                .originalFilename(Optional.ofNullable(multipartFile.getOriginalFilename()).orElse("unknown"))
                .storagePath(storagePath)
                .size(multipartFile.getSize())
                .userId(ownerId)
                .keyFingerprint(keyFingerprint)
                .uploadTime(LocalDateTime.now())
                .algorithm("AES/GCM/NoPadding")
                .downloadCount(0)
                .build();

        secureFileMapper.insert(secureFile);

        return SecureFileRespDTO.builder()
                .id(secureFile.getId())
                .originalFilename(secureFile.getOriginalFilename())
                .userId(ownerId)
                .size(secureFile.getSize())
                .uploadTime(secureFile.getUploadTime())
                .downloadUrl("/secure/download/" + secureFile.getId())
                .build();
    }

    /**
     * 实现文件下载并解密
     */
    public void downloadAndDecrypt(Long fileId, String ownerId, HttpServletResponse response) throws Exception {
        // 1. 查询元信息
        SecureFile record = baseMapper.selectById(fileId);
        if (record == null) {
            throw ExceptionUtil.toResponseStatusException("文件不存在，ID：" + fileId, HttpStatus.valueOf(HttpServletResponse.SC_NOT_FOUND));
        }
        // 2. 权限校验：仅允许文件拥有者下载（如需扩展 ACL，可自行修改）
        if (!record.getUserId().equals(ownerId)) {
            throw ExceptionUtil.toResponseStatusException("无权限下载该文件", HttpStatus.valueOf(HttpServletResponse.SC_FORBIDDEN));
        }

        // 3. 从存储路径读取加密文件
        File encryptedFile = FileStorageUtil.getEncryptedFile(record.getStoragePath());
        if (!encryptedFile.exists()) {
            throw ExceptionUtil.toResponseStatusException("存储文件丢失", HttpStatus.valueOf(HttpServletResponse.SC_INTERNAL_SERVER_ERROR));
        }

        // 4. 从 keyFingerprint 恢复原始 AES Key
        byte[] rawKey;
        try {
            rawKey = Base64.getDecoder().decode(record.getKeyFingerprint());
        } catch (IllegalArgumentException e) {
            throw ExceptionUtil.toResponseStatusException("密钥指纹解析失败", HttpStatus.valueOf(HttpServletResponse.SC_INTERNAL_SERVER_ERROR));
        }

        // 5. 设置 Response 头，准备输出
        response.setContentType("application/octet-stream");
        // 设置浏览器下载时的文件名
        String downloadName = record.getOriginalFilename();
        response.setHeader("Content-Disposition", "attachment;filename=\"" + downloadName + "\"");

        // 6. 解密并写入 response 输出流
        try (InputStream fis = Files.newInputStream(encryptedFile.toPath());
             OutputStream os = response.getOutputStream()) {

            CryptoUtil.decrypt(fis, os, rawKey);
            os.flush();
        } catch (Exception e) {
            throw ExceptionUtil.toResponseStatusException("文件解密失败：" + ExceptionUtil.getStackTrace(e),
                    HttpStatus.valueOf(HttpServletResponse.SC_INTERNAL_SERVER_ERROR));
        }

        // 7. 更新下载次数
        secureFileMapper.incrementDownloadCount(fileId);
    }

    @Override
    public List<SecureFileRespDTO> listFile() {
        LambdaQueryWrapper<SecureFile> queryWrapper = Wrappers.lambdaQuery(SecureFile.class)
                .orderByDesc(SecureFile::getId);
        List<SecureFile> files = baseMapper.selectList(queryWrapper);
        List<SecureFileRespDTO> resp = new ArrayList<>();
        for (SecureFile file : files) {
            SecureFileRespDTO secureFileRespDTO = SecureFileRespDTO.builder()
                    .userId(file.getUserId())
                    .downloadUrl("/secure/download/" + file.getId())
                    .id(file.getId())
                    .size(file.getSize())
                    .originalFilename(file.getOriginalFilename())
                    .uploadTime(file.getUploadTime())
                    .build();
            resp.add(secureFileRespDTO);
        }
        return resp;
    }

    @Override
    public void deleteFile(Long fileId) {
        // 1. 查询数据库记录
        SecureFile secureFile = secureFileMapper.selectById(fileId);
        if (secureFile == null) {
            throw ExceptionUtil.toResponseStatusException("文件不存在，ID：" + fileId, HttpStatus.NOT_FOUND);
        }

        // 2. 删除本地存储的加密文件
        File encryptedFile = FileStorageUtil.getEncryptedFile(secureFile.getStoragePath());
        if (encryptedFile.exists()) {
            try {
                Files.delete(encryptedFile.toPath());
            } catch (IOException e) {
                log.warn("删除加密文件失败: {}", encryptedFile.getAbsolutePath(), e);
                throw ExceptionUtil.toResponseStatusException("删除文件失败", HttpStatus.INTERNAL_SERVER_ERROR);
            }
        } else {
            log.warn("加密文件已不存在: {}", encryptedFile.getAbsolutePath());
        }

        // 3. 删除数据库记录
        int deleted = secureFileMapper.deleteById(fileId);
        if (deleted == 0) {
            throw ExceptionUtil.toResponseStatusException("数据库记录删除失败", HttpStatus.INTERNAL_SERVER_ERROR);
        }

        log.info("文件删除成功，ID: {}", fileId);
    }
}
